package com.wl.springsecuritystudy.config;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.access.AccessDecisionVoter;
import org.springframework.security.access.vote.AuthenticatedVoter;
import org.springframework.security.access.vote.UnanimousBased;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.access.ExceptionTranslationFilter;
import org.springframework.security.web.access.expression.WebExpressionVoter;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;


@Slf4j
@Configuration
@EnableConfigurationProperties(value = SecurityProperties.class)
public class BrowserSecurityConfig extends WebSecurityConfigurerAdapter {

    @Resource
    private SecurityProperties securityProperties;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.cors()
                .and()
                .csrf()
                .disable()
                .headers()
                .frameOptions()
                .disable()
                .and()
                .sessionManagement()
                //禁用session
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .authorizeRequests()
                // 自定义accessDecisionManager
                .accessDecisionManager(accessDecisionManager()) //访问决策管理器。
                .anyRequest()
                .authenticated()
                .and()
                .exceptionHandling()
                .accessDeniedHandler(unauthorizedAccessDeniedHandler())
                .authenticationEntryPoint(unauthorizedEntryPoint());

        http.addFilterBefore(new JwtAuthenticationFilter(authenticationManager(), unauthorizedEntryPoint()), ExceptionTranslationFilter.class);


    }



    /**
     * Web层面的配置，一般用来配置无需安全检查的路径
     * <p>
     * 这也将完全禁用该请求路径的安全过滤器链，不会经过任何的filter
     *
     * @param web
     */
    @Override
    public void configure(WebSecurity web) {

        WebSecurity.IgnoredRequestConfigurer configurer = web.ignoring()
                .antMatchers("/error", "/js/**", "/css/**", "/images/**", "/**/favicon.ico");

        if (CollectionUtils.isNotEmpty(securityProperties.getIgnoring())) {
            configurer.antMatchers(securityProperties.getIgnoring().toArray(new String[securityProperties.getIgnoring().size()]));
        }

        // web.debug(true);
    }

    /**
     * 替换默认的AccessDecisionManager
     * <p>
     * accessDecisionManager是决策管理器，这里直接使用了spring-security提供的AffirmativeBased决策管理器<br>
     * Spring提供了3个决策管理器：<br>
     * AffirmativeBased ：一票通过，只要有一个投票器通过就允许访问<br>
     * ConsensusBased ：有一半以上投票器通过才允许访问资源<br>
     * UnanimousBased ：所有投票器都通过才允许访问<br>
     *
     * @return
     */
    @Bean
    public AccessDecisionManager accessDecisionManager() {
        List<AccessDecisionVoter<? extends Object>> decisionVoters = new ArrayList<>();
        decisionVoters.add(new WebExpressionVoter());
                // new RoleVoter(),
        decisionVoters.add( new RestfulVoter());
        decisionVoters.add(new AuthenticatedVoter());

        return new UnanimousBased(decisionVoters);
    }


    @Bean
    public AccessDeniedHandler unauthorizedAccessDeniedHandler() {

        return (request, response, accessDeniedException) -> {
            log.error(accessDeniedException.getMessage(), accessDeniedException);
            response401JsonUtf8Value(response,"未授权或授权不正确");
        };
    }


    /**
     * 401响应,utf-8格式的json信息
     *
     * @param response
     * @param content
     */
    public static void response401JsonUtf8Value(HttpServletResponse response, String content) {

        response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
        // 设置服务器端的编码
        response.setCharacterEncoding("utf-8");
        response.setContentType(MediaType.APPLICATION_JSON_VALUE);
        try {
            response.getWriter().write(content);
            response.getWriter().close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }


    /**
     * 未授权的请求都统一设置HTTP状态为401
     *
     * @return
     */
    @Bean
    public AuthenticationEntryPoint unauthorizedEntryPoint() {

        return (request, response, authException) -> {
            log.error(authException.getMessage(), authException);
            response401JsonUtf8Value(response, "未授权或授权不正确");
        };
    }
}
